package org.hepeng.workx.util.proxy;

import lombok.AllArgsConstructor;
import lombok.Data;
import org.apache.commons.collections.CollectionUtils;
import org.hepeng.workx.util.ConstructorUtils;
import org.springframework.util.ClassUtils;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.List;

/**
 * @author he peng
 */

public class JdkProxyFactory extends AbstractProxyFactory {

    @Data
    @AllArgsConstructor
    private class JdkInvocationHandler implements InvocationHandler {

        private Object nativeObject;
        private List<Invoker> invokers;
        private List<InvokeFilter> filters;

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Invocation lastInvocation = new JdkInvocation(nativeObject , proxy , method , args);
            Invocation invocation = invocationChain(invokers , -1 , lastInvocation);
            Invoker invoker = ivk -> {
                if (CollectionUtils.isNotEmpty(filters)) {
                    for (InvokeFilter filter : filters) {
                        if (! filter.isProxyInvoke(ivk.getMethod())) {
                            return lastInvocation.invoke();
                        }
                    }
                }
                return ivk.invoke();
            };
            return invoker.invoke(invocation);
        }
    }

    @Override
    protected Invocation nextInvocation(List<Invoker> invokers, int index, Invocation lastInvocation) {
        Invoker nextInvoker = nextInvoker(invokers, index);
        Invocation invocation = new JdkInvocation(
                lastInvocation.getNative() , lastInvocation.getProxy() ,
                lastInvocation.getMethod() , lastInvocation.getArgs()) {
            @Override
            public Object invoke() throws Throwable {
                return nextInvoker.invoke(invocationChain(invokers , index + 1 , lastInvocation));
            }
        };
        return invocation;
    }

    @Override
    protected Object doCreateProxyInternal(
            Class<?> superClassOfProxy, List<Class<?>> interfaces,
            List<Class<?>> constructorArgTypes, List<Object> constructorArgs,
            List<Invoker> invokers, List<InvokeFilter> filters) throws Exception {

        Object[] args = ConstructorUtils.argToArray(constructorArgs);
        Class<?>[] argTypes = ConstructorUtils.argTypeToArray(constructorArgTypes);
        Object nativeObject = org.apache.commons.lang3.reflect.ConstructorUtils.invokeConstructor(superClassOfProxy, args, argTypes);
        return doCreateProxyInternal(nativeObject , superClassOfProxy , interfaces , invokers , filters);
    }

    @Override
    protected Object doCreateProxyInternal(
            Object target, Class<?> superClassOfProxy,
            List<Class<?>> interfaces, List<Invoker> invokers, List<InvokeFilter> filters) throws Exception {
        Class<?>[] ifs = new Class[interfaces.size()];
        interfaces.toArray(ifs);
        Object proxy = Proxy.newProxyInstance(
                ClassUtils.getDefaultClassLoader(),
                ifs, new JdkInvocationHandler(target , invokers ,filters));
        return proxy;
    }
}
